package otSpaces.client;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.logging.Logger;

import otSpaces.OtSpaceFunctions;
import otSpaces.msg.Message;
import otSpaces.msg.Message.UnknownMessageTypeException;
import otSpaces.util.OrdinalBytes;

import com.sun.sgs.client.ClientChannel;
import com.sun.sgs.client.ClientChannelListener;

public class OtSpaceClient {
	public interface Listener {
		ClientChannelListener joinedTopic(String topicSpaceName,
				ClientChannel channel);

		public void leftTopicSpace(String topicSpaceName);
	}

	private class TopicChannelData implements ClientChannel {
		public ClientChannelListener _clientChannelListener;

		public final short _id;
		public final String _name;

		public TopicChannelData(final short id, final String name) {
			_id = id;
			_name = name;
		}

		@Override
		public String getName() {
			return _name;
		}

		@Override
		public void send(ByteBuffer message) throws IOException {
			final Message upstream = Message.createUpstream(_id,
					OtSpaceFunctions.foldHashCode(_name),
					Message.toByteArray(message));

			_channel.send(upstream.toBuffer());
		}

	}

	public static final Logger LOG = Logger.getLogger(OtSpaceClient.class
			.getName());

	private ClientChannel _channel;
	private final Map<Short, TopicChannelData> _channelDatas = new HashMap<Short, OtSpaceClient.TopicChannelData>();
	private Listener _listener = null;

	public final String _topicSpaceName;

	public OtSpaceClient(ClientChannel channel) {
		_channel = channel;

		_topicSpaceName = OtSpaceFunctions.topicSpaceNameGet(channel.getName());
	}

	public ClientChannelListener wrap(Listener listener) {
		assert listener != null;
		assert _listener == null;

		_listener = listener;

		return new ClientChannelListener() {

			@Override
			public void leftChannel(ClientChannel channel) {

				// nullify out channel (to block them)
				_channel = null;

				// close any and all local channels
				for (Short id : new HashSet<Short>(_channelDatas.keySet())) {
					final TopicChannelData channelData = _channelDatas.get(id);

					if (channelData._clientChannelListener != null) {
						channelData._clientChannelListener
								.leftChannel(channelData);
						_channelDatas.remove(id);
					}
				}

				// notify the listener
				_listener.leftTopicSpace(_topicSpaceName);
			}

			@Override
			public void receivedMessage(ClientChannel channel, ByteBuffer data) {

				// read the message
				try {
					final Message message = Message.create(data);
					final short id = message._topicId;

					LOG.info("[" + _topicSpaceName + "] recieved {"
							+ message._type.name() + "} on subject " + id);

					// switch on type
					switch (message._type) {
					case JOIN: {

						assert !_channelDatas.containsKey(id);

						final TopicChannelData channelData = new TopicChannelData(
								message._topicId, message._topicName);

						// record the id->name
						_channelDatas.put(id, channelData);

						// doJoin
						channelData._clientChannelListener = _listener
								.joinedTopic(_topicSpaceName, channelData);

					}
						break;

					case LEAVE: {

						assert _channelDatas.containsKey(id);

						// doLeave
						final TopicChannelData channelData = _channelDatas
								.get(id);

						// if the listener cares about the data
						if (channelData._clientChannelListener != null) {
							channelData._clientChannelListener
									.leftChannel(channelData);
						}

						// remove record
						_channelDatas.remove(id);

					}
						break;

					case DOWNSTREAM: {

						assert _channelDatas.containsKey(id);

						// get our recordy thingie
						final TopicChannelData channelData = _channelDatas
								.get(id);

						// if the listener cares about the data
						if (channelData._clientChannelListener != null) {

							// get the contents
							final ByteBuffer contents = ByteBuffer
									.wrap(message._contents);

							// pass them along
							channelData._clientChannelListener.receivedMessage(
									channelData, contents);
						}
					}
						break;

					default:
						throw new UnknownMessageTypeException(
								OrdinalBytes.ordinalByte(message._type));

					}
				} catch (UnknownMessageTypeException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		};
	}

}
